classdef RequirementsManagementService < handle
    % RequirementsManagementService - 需求管理服务（中间层协调器）
    %
    % 功能：
    % - 协调所有需求管理相关的业务逻辑
    % - 提供统一的高层API供GUI调用
    % - 管理需求导入（CSV/ReqIF/ReqIR）、导出、查询等完整业务流程
    % - 生成需求报告（genReqReport）
    % - 处理需求树结构和数据缓存
    % - Requirement Set 生命周期管理（打开、关闭、冲突检测）
    %
    % 设计原则：
    % - 遵循DO-178规则第4条：业务逻辑与GUI分离
    % - 遵循DO-178规则第2条：明确错误处理，不使用fallback机制
    % - 遵循DO-178规则第6条：调试友好原则，不随意添加try-catch
    %
    % 使用示例：
    %   % 获取单例实例（DEV环境）
    %   reqService = RequirementsManagementService.getInstance();
    %   
    %   % 获取单例实例（CI环境）
    %   reqService = RequirementsManagementService.getInstance('Environment', 'CI');
    %   
    %   % 导入需求
    %   [success, result] = reqService.importFromCSV(csvFilePath);
    %   [success, result] = reqService.importFromReqIF(reqifPath);
    %   [success, result] = reqService.importFromReqIR(reqirPath);
    %   
    %   % 加载需求
    %   [success, hlrData] = reqService.loadHLRRequirements();
    %   
    %   % 生成需求报告
    %   result = reqService.generateReport(slreqxPath, outputPath);
    %   
    %   % Requirement Set 生命周期管理
    %   result = reqService.closeAllRequirementSets();  % 关闭所有已加载的requirement sets
    %   result = reqService.openRequirementSet('DO_02_Requirements/specification/T3/HLR.slreqx');
    %   result = reqService.closeRequirementSet('HLR');  % 按名称关闭
    %   loadedReqSets = reqService.getLoadedRequirementSets();  % 获取所有已加载的requirement sets
    %
    % 作者：DO-178项目开发团队
    % 日期：2025-10-04
    % 最后修改：2025-10-31 - 从ProjectManagementService迁移Requirement Set管理功能
    % 版本：2.1
    
    properties (Access = private)
        projectRoot         % 项目根目录
        environment         % 运行环境：'DEV' 或 'CI'
        logger              % LoggingService实例
        
        % 缓存数据
        hlrDataCache        % HLR需求数据缓存
        srsDataCache        % SRS需求数据缓存
        reqTreeCache        % 需求树结构缓存
    end
    
    methods (Access = private)
        
        function obj = RequirementsManagementService(varargin)
            % 私有构造函数 - 初始化需求管理服务（单例模式）
            % 输入：
            %   'Environment' - 可选，运行环境 'DEV'（默认）或 'CI'
            
            % 初始化日志服务
            try
                obj.logger = LoggingService.getInstance();
            catch ME
                warning('LoggingService:Unavailable', '日志服务不可用: %s', ME.message);
                obj.logger = [];
            end
            
            % 解析输入参数
            p = inputParser;
            addParameter(p, 'Environment', 'DEV', @(x) ismember(x, {'DEV', 'CI'}));
            parse(p, varargin{:});
            
            obj.environment = p.Results.Environment;
            
            % 基于规则第2条：如果无法初始化，明确报告错误
            try
                obj.projectRoot = obj.findProjectRoot();
                fprintf('✅ RequirementsManagementService已初始化\n');
                fprintf('   项目根目录: %s\n', obj.projectRoot);
                fprintf('   运行环境: %s\n', obj.environment);
                
                if ~isempty(obj.logger)
                    obj.logger.logInfo(sprintf('RequirementsManagementService已初始化 (环境: %s)', obj.environment));
                end
            catch ME
                if ~isempty(obj.logger)
                    obj.logger.logError(sprintf('需求管理服务初始化失败: %s', ME.message));
                end
                error('RequirementsManagementService:InitFailed', ...
                    '无法初始化需求管理服务：%s', ME.message);
            end
            
            % 初始化缓存
            obj.hlrDataCache = {};
            obj.srsDataCache = {};
            obj.reqTreeCache = [];
        end
    end
    
    methods (Static, Access = public)
        function obj = getInstance(varargin)
            % 获取RequirementsManagementService的单例实例
            % 输入：
            %   'Environment' - 可选，运行环境 'DEV'（默认）或 'CI'
            % 输出：
            %   obj - RequirementsManagementService单例实例
            
            persistent instance;
            
            if isempty(instance) || ~isvalid(instance)
                instance = RequirementsManagementService(varargin{:});
                fprintf('✅ 创建RequirementsManagementService单例实例\n');
            end
            
            obj = instance;
        end
    end
    
    methods (Access = public)
        
        %% ==================== 需求导入 ====================
        
        function [success, result] = importFromCSV(obj, csvFilePath, mainFigure, messageTextArea, themeName)
            % 从CSV文件导入需求
            % 输入：
            %   csvFilePath - CSV文件路径（可选，为空则弹出选择对话框）
            %   mainFigure - 主窗口句柄（用于对话框）
            %   messageTextArea - 消息文本区域（用于显示状态）
            %   themeName - (可选) 主题名称，用于对话框样式
            % 输出：
            %   success - 导入成功标志
            %   result - 导入结果信息
            
            % 处理可选的主题参数
            if nargin < 5
                themeName = 'light'; % 默认浅色主题
            end
            
            if ~isempty(obj.logger)
                obj.logger.logInfo('开始CSV需求导入');
            end
            
            success = false;
            result = struct();
            
            try
                % 基于规则第4条：调用独立的导入函数
                if nargin < 2 || isempty(csvFilePath)
                    % 使用现有的importCSVasReq函数（带GUI交互）
                    success = importCSVasReq(mainFigure, messageTextArea, themeName);
                    if success
                        result.message = 'CSV导入成功';
                        obj.clearCache(); % 清空缓存
                        if ~isempty(obj.logger)
                            obj.logger.logInfo('CSV需求导入成功');
                        end
                    else
                        result.error = 'CSV导入失败或已取消';
                        if ~isempty(obj.logger)
                            obj.logger.logWarning('CSV需求导入失败或被取消');
                        end
                    end
                else
                    % 直接导入指定文件（用于自动化脚本）
                    result.error = '暂不支持直接指定CSV文件路径导入';
                end
                
            catch ME
                result.error = sprintf('CSV导入异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
                if ~isempty(obj.logger)
                    obj.logger.logError(sprintf('CSV导入异常: %s', ME.message));
                end
            end
        end
        
        function [success, result] = importFromReqIF(obj, reqifFilePath, mainFigure, messageTextArea)
            % 从ReqIF文件导入需求
            % 输入：
            %   reqifFilePath - ReqIF文件路径（可选）
            %   mainFigure - 主窗口句柄
            %   messageTextArea - 消息文本区域
            % 输出：
            %   success - 导入成功标志
            %   result - 导入结果信息
            
            if ~isempty(obj.logger)
                obj.logger.logInfo('开始ReqIF需求导入');
            end
            
            success = false;
            result = struct();
            
            try
                % 基于规则第4条：调用独立的导入函数
                if nargin < 2 || isempty(reqifFilePath)
                    success = importReqIFasReq(mainFigure, messageTextArea);
                    if success
                        result.message = 'ReqIF导入成功';
                        obj.clearCache();
                        if ~isempty(obj.logger)
                            obj.logger.logInfo('ReqIF需求导入成功');
                        end
                    else
                        result.error = 'ReqIF导入失败或已取消';
                        if ~isempty(obj.logger)
                            obj.logger.logWarning('ReqIF需求导入失败或被取消');
                        end
                    end
                else
                    result.error = '暂不支持直接指定ReqIF文件路径导入';
                end
                
            catch ME
                result.error = sprintf('ReqIF导入异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
                if ~isempty(obj.logger)
                    obj.logger.logError(sprintf('ReqIF导入异常: %s', ME.message));
                end
            end
        end
        
        function [success, result] = importFromReqIR(obj, reqirFilePath, mainFigure, messageTextArea)
            % 从ReqIR文件导入需求
            % 输入：
            %   reqirFilePath - ReqIR文件路径（可选）
            %   mainFigure - 主窗口句柄
            %   messageTextArea - 消息文本区域
            % 输出：
            %   success - 导入成功标志
            %   result - 导入结果信息
            
            success = false;
            result = struct();
            
            try
                % 基于规则第4条：调用独立的导入函数
                if nargin < 2 || isempty(reqirFilePath)
                    success = importReqIRasReq(mainFigure, messageTextArea);
                    if success
                        result.message = 'ReqIR导入成功';
                        obj.clearCache();
                    else
                        result.error = 'ReqIR导入失败或已取消';
                    end
                else
                    result.error = '暂不支持直接指定ReqIR文件路径导入';
                end
                
            catch ME
                result.error = sprintf('ReqIR导入异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
            end
        end
        
        function [success, reqData, columnConfig] = loadFromSLREQX(obj, slreqxFilePath)
            % 从SLREQX文件加载需求
            % 输入：
            %   slreqxFilePath - SLREQX文件路径（可选，为空则使用默认路径）
            % 输出：
            %   success - 加载成功标志
            %   reqData - 需求数据（cell array）
            %   columnConfig - 列配置信息（包含 columnNames, columnWidths, enabledColumns）
            
            success = false;
            reqData = {};
            columnConfig = struct();
            
            try
                % 如果未指定路径，使用默认路径
                if nargin < 2 || isempty(slreqxFilePath)
                    slreqxFilePath = obj.getDefaultSLREQXPath('HLR');
                end
                
                if ~isempty(obj.logger)
                    obj.logger.logInfo(sprintf('开始加载SLREQX文件: %s', slreqxFilePath));
                end
                
                % 检查文件是否存在
                if ~exist(slreqxFilePath, 'file')
                    fprintf('⚠️ SLREQX文件不存在: %s\n', slreqxFilePath);
                    if ~isempty(obj.logger)
                        obj.logger.logWarning(sprintf('SLREQX文件不存在: %s', slreqxFilePath));
                    end
                    return;
                end
                
                % 基于规则第4条：调用独立的加载函数
                % loadslreqx现在返回4个输出：success, tableData, errorMsg, columnConfig
                [loadSuccess, reqData, errorMsg, columnConfig] = loadslreqx(slreqxFilePath);
                
                if loadSuccess && ~isempty(reqData)
                    success = true;
                    fprintf('✅ 成功加载SLREQX文件: %s (共%d条需求，%d列)\n', ...
                        slreqxFilePath, size(reqData, 1), length(columnConfig.columnNames));
                    if ~isempty(obj.logger)
                        obj.logger.logInfo(sprintf('SLREQX加载成功: %d条需求, %d列', ...
                            size(reqData, 1), length(columnConfig.columnNames)));
                    end
                else
                    if ~isempty(errorMsg)
                        fprintf('⚠️ 加载失败: %s\n', errorMsg);
                        if ~isempty(obj.logger)
                            obj.logger.logWarning(sprintf('SLREQX加载失败: %s', errorMsg));
                        end
                    end
                end
                
            catch ME
                fprintf('❌ 加载SLREQX文件异常: %s\n', ME.message);
                if ~isempty(obj.logger)
                    obj.logger.logError(sprintf('SLREQX加载异常: %s', ME.message));
                end
            end
        end
        
        %% ==================== 需求报告生成 ====================
        
        function result = generateReport(obj, slreqxPath, authorNames)
            % 生成需求报告（调用genReqReport）
            % 输入：
            %   slreqxPath - SLREQX文件路径（完整路径或需求集名称）
            %   authorNames - (可选) 作者名称
            % 输出：
            %   result - 包含成功标志和消息的结构体
            % 基于规则第2条：明确报告错误
            % 
            % 注意：genReqReport的参数是(reqSetName, authorNames, 'CI')
            
            result = struct('success', false, 'message', '', 'reportPath', '');
            
            if nargin < 2 || isempty(slreqxPath)
                error('RequirementsManagementService:InvalidInput', 'SLREQX文件路径不能为空');
            end
            
            % 处理可选参数
            if nargin < 3
                authorNames = [];
            end
            
            if ~isempty(obj.logger)
                obj.logger.logInfo(sprintf('开始生成需求报告: %s', slreqxPath));
            end
            
            try
                fprintf('📋 正在生成需求报告: %s\n', slreqxPath);
                
                % 如果传入的是完整路径，提取需求集名称
                [~, reqSetName, ext] = fileparts(slreqxPath);
                if strcmpi(ext, '.slreqx')
                    % 如果有扩展名，只使用名称
                    reqSetName = reqSetName;
                else
                    % 如果没有扩展名，可能就是需求集名称
                    reqSetName = slreqxPath;
                end
                
                % 验证文件存在
                if ~exist([reqSetName, '.slreqx'], 'file')
                    error('SLREQX文件不存在: %s.slreqx', reqSetName);
                end
                
                % 基于规则第4条：调用独立的报告生成工具函数
                % genReqReport的签名：function varargout = genReqReport(reqSetName, varargin)
                % varargin{1} = authorNames, varargin{2} = 'CI'
                if strcmpi(obj.environment, 'CI')
                    reqResult = genReqReport(reqSetName, authorNames, 'CI');
                else
                    reqResult = genReqReport(reqSetName, authorNames);
                end
                
                % 解析结果
                % genReqReport返回的result只有Message字段，如果执行成功会包含"successfully generated"
                if isfield(reqResult, 'Message') && contains(lower(reqResult.Message), 'successfully')
                    result.success = true;
                    result.message = reqResult.Message;
                    % 基于规则第3条：使用getRptPath()从配置文件读取格式
                    % 注意：genReqReport不需要modelName，需求报告路径使用特殊处理
                    result.reportPath = getRptPath('requirements', reqSetName);
                    fprintf('✅ 需求报告生成成功: %s\n', result.reportPath);
                    if ~isempty(obj.logger)
                        obj.logger.logInfo(sprintf('需求报告生成成功: %s', result.reportPath));
                    end
                elseif isfield(reqResult, 'Message')
                    result.success = false;
                    result.message = reqResult.Message;
                    fprintf('⚠️ 需求报告生成失败: %s\n', result.message);
                    if ~isempty(obj.logger)
                        obj.logger.logWarning(sprintf('需求报告生成失败: %s', result.message));
                    end
                else
                    result.success = true;
                    result.message = '需求报告已生成';
                    fprintf('✅ 需求报告生成完成\n');
                end
                
            catch ME
                result.success = false;
                result.message = sprintf('需求报告生成失败: %s', ME.message);
                result.error = ME;
                error('RequirementsManagementService:GenerateReportFailed', result.message);
            end
        end
        
        %% ==================== 需求导出 ====================
        
        function [success, errorMsg] = exportToWord(obj, slreqxPath, wordPath)
            % 导出需求到Word文档
            % 输入：
            %   slreqxPath - SLREQX文件路径
            %   wordPath - Word文档输出路径
            % 输出：
            %   success - 导出成功标志
            %   errorMsg - 错误信息（成功时为空）
            
            success = false;
            errorMsg = '';
            
            try
                % 基于规则第2条：明确验证输入
                if nargin < 2 || isempty(slreqxPath)
                    errorMsg = 'SLREQX文件路径不能为空';
                    return;
                end
                
                if nargin < 3 || isempty(wordPath)
                    errorMsg = 'Word文档输出路径不能为空';
                    return;
                end
                
                % 检查SLREQX文件是否存在
                if ~exist(slreqxPath, 'file')
                    errorMsg = sprintf('SLREQX文件不存在: %s', slreqxPath);
                    return;
                end
                
                % 规范化路径，避免相对路径导致的重复加载问题
                % 将路径转换为绝对路径，消除 ../ 等相对路径符号
                [slreqxDir, slreqxName, slreqxExt] = fileparts(slreqxPath);
                if isempty(slreqxDir)
                    % 如果只有文件名，使用当前目录
                    slreqxDir = pwd;
                end
                % 使用cd和pwd获取规范化的绝对路径
                currentDir = pwd;
                try
                    cd(slreqxDir);
                    normalizedDir = pwd;
                    cd(currentDir);
                catch
                    normalizedDir = slreqxDir;
                    cd(currentDir);
                end
                normalizedPath = fullfile(normalizedDir, [slreqxName, slreqxExt]);
                
                % 调用需求工具箱的导出功能
                fprintf('正在导出需求到Word文档...\n');
                fprintf('规范化路径: %s\n', normalizedPath);
                
                % 基于规则第9条：参考reqtb.md API文档
                % 检查需求集是否已经加载
                reqSet = [];
                try
                    % 尝试查找已加载的需求集
                    allReqSets = slreq.find('Type', 'ReqSet');
                    for i = 1:length(allReqSets)
                        % 规范化已加载需求集的路径
                        loadedPath = allReqSets(i).Filename;
                        [loadedDir, loadedName, loadedExt] = fileparts(loadedPath);
                        if ~isempty(loadedDir)
                            origDir = pwd;
                            try
                                cd(loadedDir);
                                normalizedLoadedDir = pwd;
                                cd(origDir);
                            catch
                                normalizedLoadedDir = loadedDir;
                                cd(origDir);
                            end
                            normalizedLoadedPath = fullfile(normalizedLoadedDir, [loadedName, loadedExt]);
                            
                            % 比较规范化后的路径
                            if strcmpi(normalizedLoadedPath, normalizedPath)
                                reqSet = allReqSets(i);
                                fprintf('使用已加载的需求集: %s\n', loadedPath);
                                break;
                            end
                        end
                    end
                catch ME
                    fprintf('查找已加载需求集时出错: %s\n', ME.message);
                end
                
                % 如果未找到已加载的需求集，则加载
                if isempty(reqSet)
                    fprintf('加载需求集: %s\n', normalizedPath);
                    reqSet = slreq.load(normalizedPath);
                end
                
                % 获取默认报告选项结构
                opts = slreq.getReportOptions;
                
                % 设置输出路径为.docx
                opts.reportPath = wordPath;
                
                % 配置报告选项
                opts.includes.toc = true;           % 启用目录
                opts.includes.comments = true;       % 包含注释
                opts.includes.links = true;          % 包含链接
                
                % 执行报告生成
                reportFullPath = slreq.generateReport(reqSet, opts);
                
                success = true;
                fprintf('✅ 需求已成功导出到: %s\n', reportFullPath);
                
            catch ME
                errorMsg = sprintf('导出Word文档异常: %s', ME.message);
                fprintf('❌ %s\n', errorMsg);
            end
        end
        
        %% ==================== 需求查询 ====================
        
        function [success, hlrData] = loadHLRRequirements(obj, forceRefresh)
            % 加载HLR需求（带缓存）
            % 输入：
            %   forceRefresh - 是否强制刷新（忽略缓存）
            % 输出：
            %   success - 加载成功标志
            %   hlrData - HLR需求数据（cell array）
            
            if nargin < 2
                forceRefresh = false;
            end
            
            % 如果有缓存且不强制刷新，返回缓存
            if ~forceRefresh && ~isempty(obj.hlrDataCache)
                success = true;
                hlrData = obj.hlrDataCache;
                fprintf('✅ 从缓存加载HLR需求 (共%d条)\n', size(hlrData, 1));
                return;
            end
            
            % 从SLREQX文件加载
            hlrPath = obj.getDefaultSLREQXPath('HLR');
            [success, hlrData] = obj.loadFromSLREQX(hlrPath);
            
            if success
                obj.hlrDataCache = hlrData;
            end
        end
        
        function [success, srsData] = loadSRSRequirements(obj, forceRefresh)
            % 加载SRS需求（带缓存）
            % 输入：
            %   forceRefresh - 是否强制刷新
            % 输出：
            %   success - 加载成功标志
            %   srsData - SRS需求数据（cell array）
            
            if nargin < 2
                forceRefresh = false;
            end
            
            % 如果有缓存且不强制刷新，返回缓存
            if ~forceRefresh && ~isempty(obj.srsDataCache)
                success = true;
                srsData = obj.srsDataCache;
                fprintf('✅ 从缓存加载SRS需求 (共%d条)\n', size(srsData, 1));
                return;
            end
            
            % 从SLREQX文件加载
            srsPath = obj.getDefaultSLREQXPath('SR');
            [success, srsData] = obj.loadFromSLREQX(srsPath);
            
            if success
                obj.srsDataCache = srsData;
            end
        end
        
        %% ==================== 需求树管理 ====================
        
        function [success, reqTree] = buildRequirementsTree(obj)
            % 构建需求树结构
            % 输出：
            %   success - 构建成功标志
            %   reqTree - 需求树数据结构
            
            success = false;
            reqTree = [];
            
            try
                % 基于规则第2条：明确处理错误
                reqDir = fullfile(obj.projectRoot, 'DO_02_Requirements', 'specification');
                
                if ~exist(reqDir, 'dir')
                    fprintf('⚠️ 需求目录不存在: %s\n', reqDir);
                    return;
                end
                
                % 构建树结构
                reqTree = obj.scanRequirementsDirectory(reqDir);
                
                if ~isempty(reqTree)
                    success = true;
                    obj.reqTreeCache = reqTree;
                    fprintf('✅ 成功构建需求树结构\n');
                end
                
            catch ME
                fprintf('❌ 构建需求树异常: %s\n', ME.message);
            end
        end
        
        %% ==================== 追溯矩阵 ====================
        
        function [success, result] = viewTraceMatrix(obj)
            % 查看追溯矩阵
            % 输出：
            %   success - 查看成功标志
            %   result - 结果信息
            
            success = false;
            result = struct();
            
            try
                % 基于规则第2条：明确处理文件不存在的情况
                hlrPath = obj.getDefaultSLREQXPath('HLR');
                
                if ~exist(hlrPath, 'file')
                    result.error = sprintf('HLR.slreqx文件不存在: %s', hlrPath);
                    fprintf('❌ %s\n', result.error);
                    return;
                end
                
                % 打开需求文件并显示追溯矩阵
                fprintf('正在打开追溯矩阵...\n');
                reqSet = slreq.open(hlrPath);
                slreq.view(reqSet);
                
                success = true;
                result.message = '追溯矩阵已打开';
                fprintf('✅ %s\n', result.message);
                
            catch ME
                result.error = sprintf('打开追溯矩阵异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
            end
        end
        
        %% ==================== Requirement Set 生命周期管理 ====================
        
        function result = closeAllRequirementSets(obj)
            % 关闭所有已加载的 Requirement Sets
            % 输出：
            %   result - 包含成功标志和关闭数量的结构体
            % 基于规则第2条：明确报告错误
            % 基于规则第9条：参考 requirement toolbox API
            
            result = struct('success', false, 'message', '', 'closedCount', 0, 'details', []);
            
            try
                fprintf('🔍 检查已加载的 Requirement Sets...\n');
                
                % 使用slreq.find获取所有已加载的requirement sets
                try
                    loadedReqSets = slreq.find('Type', 'ReqSet');
                catch
                    % 如果没有加载任何requirement set，find可能会失败
                    loadedReqSets = [];
                end
                
                if isempty(loadedReqSets)
                    result.success = true;
                    result.message = '没有已加载的 Requirement Sets';
                    fprintf('✅ 没有需要关闭的 Requirement Sets\n');
                    return;
                end
                
                fprintf('  找到 %d 个已加载的 Requirement Sets\n', length(loadedReqSets));
                
                closedDetails = cell(length(loadedReqSets), 1);
                closedCount = 0;
                
                for i = 1:length(loadedReqSets)
                    reqSet = loadedReqSets(i);
                    reqSetName = reqSet.Name;
                    reqSetPath = char(reqSet.Filename);
                    
                    fprintf('  [%d/%d] 准备关闭: %s\n', i, length(loadedReqSets), reqSetName);
                    
                    try
                        % 检查是否有未保存的修改
                        if reqSet.Dirty
                            fprintf('    ⚠️  警告: Requirement Set 有未保存的修改\n');
                            closedDetails{i} = struct(...
                                'name', reqSetName, ...
                                'path', reqSetPath, ...
                                'closed', false, ...
                                'reason', '有未保存的修改，需要先保存');
                            continue;
                        end
                        
                        % 关闭requirement set - 使用文件路径
                        slreq.close(reqSetPath);
                        closedCount = closedCount + 1;
                        
                        closedDetails{i} = struct(...
                            'name', reqSetName, ...
                            'path', reqSetPath, ...
                            'closed', true, ...
                            'reason', '');
                        
                        fprintf('    ✅ 已关闭\n');
                        
                    catch ME
                        fprintf('    ❌ 关闭失败: %s\n', ME.message);
                        closedDetails{i} = struct(...
                            'name', reqSetName, ...
                            'path', reqSetPath, ...
                            'closed', false, ...
                            'reason', ME.message);
                    end
                end
                
                % 移除空元素
                closedDetails = closedDetails(~cellfun(@isempty, closedDetails));
                
                result.success = true;
                result.message = sprintf('成功关闭 %d/%d 个 Requirement Sets', ...
                    closedCount, length(loadedReqSets));
                result.closedCount = closedCount;
                result.details = closedDetails;
                
                fprintf('✅ %s\n', result.message);
                
                if ~isempty(obj.logger)
                    obj.logger.logInfo(result.message);
                end
                
            catch ME
                result.success = false;
                result.message = sprintf('关闭 Requirement Sets 失败: %s', ME.message);
                result.error = ME;
                
                fprintf('❌ %s\n', result.message);
                
                if ~isempty(obj.logger)
                    obj.logger.logError(result.message);
                end
                
                % 基于规则第2条：明确报告错误
                error('RequirementsManagementService:CloseRequirementSetsFailed', result.message);
            end
        end
        
        function result = openRequirementSet(obj, reqxPath, varargin)
            % 安全地打开 Requirement Set
            % 输入：
            %   reqxPath - SLREQX文件路径（支持相对路径和绝对路径）
            %   varargin - 可选参数（Name-Value pairs）
            %     'AutoCloseConflict' - 是否自动关闭同名的requirement set（默认true）
            % 输出：
            %   result - 包含成功标志、reqSet对象和消息的结构体
            % 基于规则第2条：明确报告错误
            % 基于规则第9条：参考 requirement toolbox API
            
            result = struct('success', false, 'message', '', 'reqSet', [], 'normalizedPath', '');
            
            if nargin < 2 || isempty(reqxPath)
                error('RequirementsManagementService:InvalidInput', 'SLREQX文件路径不能为空');
            end
            
            % 解析可选参数
            p = inputParser;
            addParameter(p, 'AutoCloseConflict', true, @islogical);
            parse(p, varargin{:});
            
            autoCloseConflict = p.Results.AutoCloseConflict;
            
            try
                fprintf('📂 正在打开 Requirement Set: %s\n', reqxPath);
                
                % 1. 规范化路径
                fprintf('  [1/4] 规范化文件路径...\n');
                normalizedPath = normalizeReqxPath(reqxPath);
                result.normalizedPath = normalizedPath;
                fprintf('    规范化路径: %s\n', normalizedPath);
                
                % 2. 检查同名的requirement set
                fprintf('  [2/4] 检查已加载的 Requirement Sets...\n');
                [hasConflict, conflictReqSet] = obj.checkRequirementSetConflict(normalizedPath);
                
                if hasConflict
                    fprintf('    ⚠️  发现同名 Requirement Set 已加载\n');
                    
                    if autoCloseConflict
                        fprintf('    正在关闭冲突的 Requirement Set...\n');
                        
                        % 检查是否有未保存的修改
                        if conflictReqSet.Dirty
                            error('RequirementsManagementService:UnsavedChanges', ...
                                'Requirement Set "%s" 有未保存的修改，无法自动关闭。\n请先保存或手动关闭。', ...
                                conflictReqSet.Name);
                        end
                        
                        % 关闭冲突的requirement set - 使用文件路径
                        slreq.close(char(conflictReqSet.Filename));
                        fprintf('    ✅ 已关闭冲突的 Requirement Set\n');
                    else
                        error('RequirementsManagementService:RequirementSetConflict', ...
                            'Requirement Set "%s" 已经加载，路径: %s', ...
                            conflictReqSet.Name, char(conflictReqSet.Filename));
                    end
                end
                
                % 3. 打开requirement set
                fprintf('  [3/4] 加载 Requirement Set...\n');
                reqSet = slreq.load(normalizedPath);
                
                if isempty(reqSet)
                    error('无法加载 Requirement Set，返回为空');
                end
                
                % 4. 验证加载结果
                fprintf('  [4/4] 验证加载结果...\n');
                result.success = true;
                result.message = sprintf('成功打开 Requirement Set: %s', reqSet.Name);
                result.reqSet = reqSet;
                
                fprintf('✅ Requirement Set 已打开\n');
                fprintf('   名称: %s\n', reqSet.Name);
                fprintf('   路径: %s\n', char(reqSet.Filename));
                fprintf('   需求数量: %d\n', length(reqSet.find('Type', 'Requirement')));
                
                if ~isempty(obj.logger)
                    obj.logger.logInfo(sprintf('打开 Requirement Set: %s', reqSet.Name));
                end
                
            catch ME
                result.success = false;
                result.message = sprintf('打开 Requirement Set 失败: %s', ME.message);
                result.error = ME;
                
                fprintf('❌ %s\n', result.message);
                
                if ~isempty(obj.logger)
                    obj.logger.logError(result.message);
                end
                
                % 基于规则第2条：明确报告错误
                error('RequirementsManagementService:OpenRequirementSetFailed', result.message);
            end
        end
        
        function result = closeRequirementSet(obj, nameOrPath)
            % 关闭特定的 Requirement Set
            % 输入：
            %   nameOrPath - Requirement Set的名称或路径
            % 输出：
            %   result - 包含成功标志和消息的结构体
            % 基于规则第2条：明确报告错误
            % 基于规则第9条：参考 requirement toolbox API
            
            result = struct('success', false, 'message', '', 'name', '', 'path', '');
            
            if nargin < 2 || isempty(nameOrPath)
                error('RequirementsManagementService:InvalidInput', 'Requirement Set名称或路径不能为空');
            end
            
            try
                fprintf('🔒 正在关闭 Requirement Set: %s\n', nameOrPath);
                
                % 1. 查找requirement set
                fprintf('  [1/3] 查找 Requirement Set...\n');
                reqSet = obj.findRequirementSet(nameOrPath);
                
                if isempty(reqSet)
                    error('未找到 Requirement Set: %s', nameOrPath);
                end
                
                fprintf('    找到: %s (路径: %s)\n', reqSet.Name, char(reqSet.Filename));
                
                % 2. 检查未保存的修改
                fprintf('  [2/3] 检查未保存的修改...\n');
                if reqSet.Dirty
                    error('RequirementsManagementService:UnsavedChanges', ...
                        'Requirement Set "%s" 有未保存的修改，无法关闭。\n请先保存或使用 save() 方法。', ...
                        reqSet.Name);
                end
                
                % 3. 关闭requirement set - 使用文件路径
                fprintf('  [3/3] 关闭 Requirement Set...\n');
                reqSetPath = char(reqSet.Filename);
                slreq.close(reqSetPath);
                
                result.success = true;
                result.message = sprintf('成功关闭 Requirement Set: %s', reqSet.Name);
                result.name = reqSet.Name;
                result.path = char(reqSet.Filename);
                
                fprintf('✅ Requirement Set 已关闭\n');
                
                if ~isempty(obj.logger)
                    obj.logger.logInfo(result.message);
                end
                
            catch ME
                result.success = false;
                result.message = sprintf('关闭 Requirement Set 失败: %s', ME.message);
                result.error = ME;
                
                fprintf('❌ %s\n', result.message);
                
                if ~isempty(obj.logger)
                    obj.logger.logError(result.message);
                end
                
                % 基于规则第2条：明确报告错误
                error('RequirementsManagementService:CloseRequirementSetFailed', result.message);
            end
        end
        
        function loadedReqSets = getLoadedRequirementSets(obj)
            % 获取所有已加载的 Requirement Sets
            % 输出：
            %   loadedReqSets - Requirement Set对象数组
            % 基于规则第9条：参考 requirement toolbox API
            
            try
                % 使用slreq.find获取所有已加载的requirement sets
                loadedReqSets = slreq.find('Type', 'ReqSet');
            catch
                % 如果没有加载任何requirement set，返回空数组
                loadedReqSets = [];
            end
        end
        
        %% ==================== 缓存管理 ====================
        
        function clearCache(obj)
            % 清空所有缓存
            obj.hlrDataCache = {};
            obj.srsDataCache = {};
            obj.reqTreeCache = [];
            fprintf('✅ 需求管理服务缓存已清空\n');
        end
        
    end
    
    methods (Access = private)
        
        function projectRoot = findProjectRoot(obj)
            % 查找项目根目录
            currentPath = fileparts(mfilename('fullpath'));
            
            % 向上查找包含DO_02_Requirements目录的路径
            while ~isempty(currentPath)
                reqDir = fullfile(currentPath, 'DO_02_Requirements');
                if exist(reqDir, 'dir')
                    projectRoot = currentPath;
                    return;
                end
                
                parentPath = fileparts(currentPath);
                if strcmp(parentPath, currentPath)
                    break;
                end
                currentPath = parentPath;
            end
            
            % 如果找不到，使用当前工作目录
            projectRoot = pwd;
        end
        
        function slreqxPath = getDefaultSLREQXPath(obj, reqType)
            % 获取默认的SLREQX文件路径
            % 输入：reqType - 'HLR' 或 'SR'
            
            reqDir = fullfile(obj.projectRoot, 'DO_02_Requirements', 'specification');
            
            if strcmp(reqType, 'HLR')
                slreqxPath = fullfile(reqDir, 'HLR.slreqx');
            elseif strcmp(reqType, 'SR')
                slreqxPath = fullfile(reqDir, 'SR.slreqx');
            else
                slreqxPath = '';
            end
        end
        
        function treeData = scanRequirementsDirectory(obj, reqDir)
            % 扫描需求目录，构建树结构
            % 输入：reqDir - 需求目录路径
            % 输出：treeData - 树结构数据
            
            treeData = struct();
            treeData.name = 'Requirements';
            treeData.path = reqDir;
            treeData.type = 'root';
            treeData.children = {};
            
            % 扫描目录中的文件
            files = dir(reqDir);
            
            for i = 1:length(files)
                file = files(i);
                
                % 跳过. 和 ..
                if strcmp(file.name, '.') || strcmp(file.name, '..')
                    continue;
                end
                
                fullPath = fullfile(reqDir, file.name);
                
                if file.isdir
                    % 递归扫描子目录
                    childNode = obj.scanRequirementsDirectory(fullPath);
                    childNode.name = file.name;
                    treeData.children{end+1} = childNode;
                else
                    % 添加文件节点（只添加需求相关文件）
                    [~, ~, ext] = fileparts(file.name);
                    if ismember(ext, {'.slreqx', '.docx', '.pdf'})
                        fileNode = struct();
                        fileNode.name = file.name;
                        fileNode.path = fullPath;
                        fileNode.type = 'file';
                        fileNode.extension = ext;
                        treeData.children{end+1} = fileNode;
                    end
                end
            end
        end
        
        function [hasConflict, conflictReqSet] = checkRequirementSetConflict(obj, normalizedPath)
            % 检查是否存在同名的已加载 Requirement Set
            % 输入：
            %   normalizedPath - 规范化后的文件路径
            % 输出：
            %   hasConflict - 是否存在冲突
            %   conflictReqSet - 冲突的 Requirement Set对象（如果存在）
            
            hasConflict = false;
            conflictReqSet = [];
            
            try
                % 获取所有已加载的requirement sets
                loadedReqSets = obj.getLoadedRequirementSets();
                
                if isempty(loadedReqSets)
                    return;
                end
                
                % 从路径中提取文件名（不含扩展名）
                [~, targetName, ~] = fileparts(normalizedPath);
                
                % 检查每个已加载的requirement set
                for i = 1:length(loadedReqSets)
                    reqSet = loadedReqSets(i);
                    
                    % 检查名称是否匹配
                    if strcmp(reqSet.Name, targetName)
                        % 规范化已加载的requirement set的路径
                        try
                            loadedPath = normalizeReqxPath(char(reqSet.Filename));
                        catch
                            loadedPath = char(reqSet.Filename);
                        end
                        
                        % 如果路径不同，则存在冲突
                        if ~strcmpi(loadedPath, normalizedPath)
                            hasConflict = true;
                            conflictReqSet = reqSet;
                            return;
                        end
                    end
                end
                
            catch ME
                warning('RequirementsManagementService:ConflictCheckFailed', ...
                    '检查 Requirement Set 冲突时出错: %s', ME.message);
            end
        end
        
        function reqSet = findRequirementSet(obj, nameOrPath)
            % 根据名称或路径查找已加载的 Requirement Set
            % 输入：
            %   nameOrPath - Requirement Set的名称或路径
            % 输出：
            %   reqSet - Requirement Set对象（未找到返回空）
            
            reqSet = [];
            
            try
                % 获取所有已加载的requirement sets
                loadedReqSets = obj.getLoadedRequirementSets();
                
                if isempty(loadedReqSets)
                    return;
                end
                
                % 尝试按名称查找
                for i = 1:length(loadedReqSets)
                    if strcmp(loadedReqSets(i).Name, nameOrPath)
                        reqSet = loadedReqSets(i);
                        return;
                    end
                end
                
                % 尝试按路径查找
                try
                    normalizedPath = normalizeReqxPath(nameOrPath);
                    for i = 1:length(loadedReqSets)
                        try
                            loadedPath = normalizeReqxPath(char(loadedReqSets(i).Filename));
                            if strcmpi(loadedPath, normalizedPath)
                                reqSet = loadedReqSets(i);
                                return;
                            end
                        catch
                            continue;
                        end
                    end
                catch
                    % nameOrPath不是有效路径，忽略
                end
                
            catch ME
                warning('RequirementsManagementService:FindRequirementSetFailed', ...
                    '查找 Requirement Set 时出错: %s', ME.message);
            end
        end
        
    end
end




